package com.qire.antscompiler.generator;

import com.qire.antscore.annotation.RouteMapping;
import com.qire.antscore.common.AssertUtils;
import com.qire.antscore.constant.GeneratorConfig;
import com.qire.antscore.entity.RouteMeta;
import com.qire.antscompiler.utils.Log;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/**
 * 路由表加载器代码生成器
 */
public class RouterCodeGenerator {

    private final String ACTIVITY = "android.app.Activity";
    private final String FRAGMENT = "android.app.Fragment";
    private final String FRAGMENT_X = "androidx.fragment.app.Fragment";
    private final String FRAGMENT_V4 = "android.support.v4.app.Fragment";

    private final String ISERVICE = "com.qire.antsrouter.provider.IService";

    // 日志工具
    private final Log log;
    // type(类信息)工具类
    private final Types typeUtils;
    // 文件生成器 类/资源
    private final Filer filerUtils;
    // 节点工具类 (类、函数、属性都是节点)
    private final Elements elementUtils;


    // 用于build类中组成类名防止在多模块情况下冲突
    private final String moduleName;

    /**
     * key:组名 value:类名
     */
    private Map<String, String> rootMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();

    public RouterCodeGenerator(ProcessingEnvironment processingEnv,String moduleName){
        //获得apt的日志输出
        this.log            = Log.newLog(processingEnv.getMessager());
        this.typeUtils      = processingEnv.getTypeUtils();
        this.filerUtils     = processingEnv.getFiler();
        this.elementUtils   = processingEnv.getElementUtils();
        this.moduleName     = moduleName;
    }

    /**
     *
     * @param rootElements
     */
    public void processorRoute(Set<? extends Element> rootElements) {
        //获得Activity这个类的节点信息
        TypeElement activity = elementUtils.getTypeElement(ACTIVITY);
        TypeElement fragment = elementUtils.getTypeElement(FRAGMENT);
        TypeElement fragmentX = elementUtils.getTypeElement(FRAGMENT_X);
        TypeElement fragmentV4 = elementUtils.getTypeElement(FRAGMENT_V4);
        TypeElement service = elementUtils.getTypeElement(ISERVICE);

        if(AssertUtils.isNull(service)) {
            log.i("Processor Route Error: TypeElement service is null, 请检查是否引入 IService 全名：" + ISERVICE);
        }

        for (Element element : rootElements) {
            RouteMeta routeMeta;
            //类信息
            TypeMirror typeMirror = element.asType();
            log.i("RouteMapping class:" + typeMirror.toString());
            RouteMapping route = element.getAnnotation(RouteMapping.class);
            if (typeUtils.isSubtype(typeMirror, activity.asType())) {
                // 构建 Activity 路由元数据
                routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, element);
            } else if(typeUtils.isSubtype(typeMirror, fragmentX.asType())) {
                // 构建 androidx.fragment.app.Fragment 路由元数据
                routeMeta = new RouteMeta(RouteMeta.Type.FRAGMENT_X, route, element);
            } else if(typeUtils.isSubtype(typeMirror, fragment.asType())) {
                // 构建 android.app.Fragment 路由元数据
                routeMeta = new RouteMeta(RouteMeta.Type.FRAGMENT, route, element);
            }  else if(typeUtils.isSubtype(typeMirror, fragmentV4.asType())) {
                // 构建 android.support.v4.app.Fragment 路由元数据
                routeMeta = new RouteMeta(RouteMeta.Type.FRAGMENT_V4, route, element);
            } else if (typeUtils.isSubtype(typeMirror, service.asType())) {
                // 构建 com.qire.antsrouter.provider.IService 路由元数据
                routeMeta = new RouteMeta(RouteMeta.Type.ISERVICE, route, element);
            } else {
                throw new RuntimeException("注解RouteMapping目前只支持 Activity、Fragment 和 IService: " + element);
            }
            categories(routeMeta);
        }
        TypeElement iRouteGroup = elementUtils.getTypeElement(GeneratorConfig.I_ROUTE_GROUP);
        TypeElement iRouteRoot = elementUtils.getTypeElement(GeneratorConfig.I_ROUTE_ROOT);

        //生成Group记录分组表
        generatedGroup(iRouteGroup);

        //生成Root类 作用：记录<分组，对应的Group类>
        generatedRoot(iRouteRoot, iRouteGroup);
    }

    /**
     * 组别分类，将routeMeta分配到对应组别
     * @param routeMeta 待分组路由元数据
     */
    private void categories(RouteMeta routeMeta) {
        if (routeVerify(routeMeta)) {
            log.i("Group : " + routeMeta.getGroup() + " path=" + routeMeta.getPath());
            //分组与组中的路由信息
            List<RouteMeta> routeMetas = groupMap.get(routeMeta.getGroup());
            if (AssertUtils.isEmpty(routeMetas)) {
                routeMetas = new ArrayList<>();
                routeMetas.add(routeMeta);
                groupMap.put(routeMeta.getGroup(), routeMetas);
            } else {
                routeMetas.add(routeMeta);
            }
        } else {
            log.i("Group info error:" + routeMeta.getPath());
        }
    }

    /**
     *
     * 验证path路由地址及group组别的合法性,
     * group 如果没有配置 则从path截取出组名
     * @param routeMeta 待检查的路由元数据
     * @return 如果path路由地址合法且及group组别存在则返回true，否则返回false。
     */
    private boolean routeVerify(RouteMeta routeMeta) {
        String path = routeMeta.getPath();
        String group = routeMeta.getGroup();
        // 必须以 / 开头来指定路由地址，是为了后面需要从path中截取group可以统一处理，否则只写了路径也未设置group的情况下path.indexOf会返回-1引起substring报错。
        if (!path.startsWith("/")) {
            log.i("routeMapping path 必须以 / 开头来指定路由地址：" + path);
            return false;
        }
        //如果group没有设置 我们从path中获得group
        if (AssertUtils.isEmpty(group)) {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            //截取出的group还是空
            if (AssertUtils.isEmpty(defaultGroup)) {
                log.i("routeMapping 未找到group信息：" + path);
                return false;
            }
            routeMeta.setGroup(defaultGroup);
        }
        return true;
    }


    /**
     * 生成Root类  作用：记录<分组，对应的Group类>
     * @param iRouteRoot
     * @param iRouteGroup
     */
    private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) {
        //创建参数类型 Map<String,Class<? extends IRouteGroup>> routes>
        //Wildcard 通配符
        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                ));
        //参数 Map<String,Class<? extends IRouteGroup>> routes> routes
        ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, "routes").build();
        //函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(GeneratorConfig.METHOD_LOAD_INTO)
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(parameter);
        //函数体
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            methodBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(GeneratorConfig.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
        }
        //生成$Root$类
        String className = GeneratorConfig.CLASS_ROOT_PREFIX + moduleName;
        TypeSpec typeSpec = TypeSpec.classBuilder(className)
                .addSuperinterface(ClassName.get(iRouteRoot))
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodBuilder.build())
                .build();
        try {
            JavaFile.builder(GeneratorConfig.PACKAGE_OF_GENERATE_FILE, typeSpec).build().writeTo(filerUtils);
            log.i("Generated RouteRoot：" + GeneratorConfig.PACKAGE_OF_GENERATE_FILE + "." + className);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void generatedGroup(TypeElement iRouteGroup) {
        //创建参数类型 Map<String, RouteMeta>
        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class));
        ParameterSpec altas = ParameterSpec.builder(parameterizedTypeName, "atlas").build();

        for (Map.Entry<String, List<RouteMeta>> entry : groupMap.entrySet()) {
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(GeneratorConfig.METHOD_LOAD_INTO)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .addParameter(altas);

            String groupName = entry.getKey();
            List<RouteMeta> groupData = entry.getValue();
            for (RouteMeta routeMeta : groupData) {
                //函数体的添加
                methodBuilder.addStatement("atlas.put($S,$T.build($T.$L,$T.class,$S,$S))",
                        routeMeta.getPath(),
                        ClassName.get(RouteMeta.class),
                        ClassName.get(RouteMeta.Type.class),
                        routeMeta.getType(),
                        ClassName.get(((TypeElement) routeMeta.getElement())),
                        routeMeta.getPath(),
                        routeMeta.getGroup());
            }
            String groupClassName = GeneratorConfig.CLASS_GROUP_PREFIX + groupName;
            TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)
                    .addSuperinterface(ClassName.get(iRouteGroup))
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodBuilder.build())
                    .build();
            JavaFile javaFile = JavaFile.builder(GeneratorConfig.PACKAGE_OF_GENERATE_FILE, typeSpec).build();
            try {
                javaFile.writeTo(filerUtils);
            } catch (IOException e) {
                e.printStackTrace();
            }
            rootMap.put(groupName, groupClassName);

        }
    }

}
